////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNNetworking/inc/WNNetworkReadBuffer.h"
#include "WNNetworking/inc/WNNetworkManager.h"
#include "WNNetworking/inc/WNNetworkReadBuffer.h"
#include "WNNetworking/inc/WNBufferResource.h"
#include "WNContainers/inc/WNSerializer.h"
#include "WNContainers/inc/WNDataBuffer.h"
#include "WNMemory/inc/WNAllocation.h"
#include "WNMemory/inc/WNManipulation.h"
#include "WNMath/inc/WNMath.h"

using namespace WNNetworking;
using namespace WNContainers;
using namespace WNMemory;
using namespace WNMath;

WNNetworkReadBuffer::WNNetworkReadBuffer(WNNetworkManager& _manager) :
    mManager(_manager),
    mCurrentChunk(0),
    mBufferPointer(0),
    mTotalRead(0),
    mInitialized(WN_FALSE),
    mDataOverflow(WNAllocateResource<WNBufferResource>(_manager)) {
}

WN_BOOL WNNetworkReadBuffer::Serialize(WN_UINT32 _flags, const WNSerializerBase& _serializer) {
    const WN_SIZE_T read = _serializer.Serialize(*this, _flags);

    mBufferPointer += read;
    mTotalRead += read;

    WN_RELEASE_ASSERT(mTotalRead <= mTotalSize);
    
    if (mBufferPointer > MAX_DATA_WRITE) {
        mBufferPointer -= MAX_DATA_WRITE;

        mChunks.pop_front();
    }

    return(WN_TRUE);
}

WNContainers::WNDataBufferType WNNetworkReadBuffer::GetType() {
    return(WNContainers::ReadBinary);
}

WN_CHAR* WNNetworkReadBuffer::ReserveBytes(WN_SIZE_T _numBytes, WN_SIZE_T& _returnedBytes) {
    WN_RELEASE_ASSERT_DESC(_numBytes < MAX_DATA_WRITE, "Exceeded maximum data write size");

    if (mBufferPointer == MAX_DATA_WRITE) {
        mBufferPointer = 0;
        mCurrentChunk++;

        _returnedBytes = WNMin(WNContainers::MAX_DATA_WRITE, _numBytes);

        return(mChunks[mCurrentChunk]->GetPointer());
    }

    if (_numBytes + mBufferPointer > MAX_DATA_WRITE) {
        const WN_SIZE_T overflowAmount = _numBytes + mBufferPointer - MAX_DATA_WRITE;

        mDataOverflow->Clear();

        WNMemCpyT<WN_CHAR>(mDataOverflow->GetPointer(), mChunks[mCurrentChunk]->GetPointer(), _numBytes - overflowAmount);
        WNMemCpyT<WN_CHAR>(mDataOverflow->GetPointer() + _numBytes - overflowAmount, mChunks[mCurrentChunk]->GetPointer(), overflowAmount);

        _returnedBytes = _returnedBytes;

        return(mDataOverflow->GetPointer());
    }

    _returnedBytes = _numBytes;

    return(mChunks[mCurrentChunk]->GetPointer());
}

WN_VOID WNNetworkReadBuffer::AppendBuffer(WNResourcePointer<WNBufferResource>&  _buffer, WN_SIZE_T _datacount, WN_SIZE_T _dataOffset) {
    WN_RELEASE_ASSERT_DESC(_datacount < MAX_DATA_WRITE, "You cannot append a buffer larger than the set size");

    mInitialized = WN_TRUE;
    
    if (mChunks.empty()) {
        mWriteOffset = _dataOffset;
        mBufferPointer = _dataOffset;
    } else {
        WN_RELEASE_ASSERT_DESC(_dataOffset == 0, "Cannot append a buffer to a position other than the start of the stream if it has a space at the start");
    }
    
    mChunks.push_back(_buffer);
    
    if ((_datacount + _dataOffset) != MAX_DATA_WRITE) {
        WN_RELEASE_ASSERT_DESC(!mLastChunk, "Error trying to append a non-full buffer to a write stream");

        mLastChunk = WN_TRUE;
    }

    mTotalSize += _datacount;
}

WN_BOOL WNNetworkReadBuffer::Initialized() {
    return(mInitialized);
}

WN_VOID WNNetworkReadBuffer::Clear() {
    mInitialized = WN_FALSE;
    mTotalSize = mWriteOffset = mBufferPointer = mTotalRead = mTotalSize = 0;
    mLastChunk = WN_FALSE;

    mChunks.clear();
}

WN_CHAR* WNNetworkReadBuffer::GetLastBuffer() {
    WN_DEBUG_ASSERT(mChunks.size() > 0);

    return(mChunks.back()->GetBaseLocation());
}

WN_VOID WNNetworkReadBuffer::PrepareRead() {
    mChunks.back()->PrepareRead();
}
